package com.xayy.health.mgt.service.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.kawins.base.result.CheckResult;
import cn.kawins.base.utils.CollUtil;
import com.alibaba.fastjson.JSON;
import com.xayy.health.core.enums.ModuleEnum;
import com.xayy.health.core.enums.RunningStatusEnum;
import com.xayy.health.core.enums.TaskStatusEnum;
import com.xayy.health.core.exception.HealthException;
import com.xayy.health.mgt.mapper.SatelliteNodeMapper;
import com.xayy.health.mgt.model.TaskChildInfo;
import com.xayy.health.mgt.model.TaskInfo;
import com.xayy.health.mgt.model.TbDatatransmissionplan;
import com.xayy.health.mgt.model.TelemetryData;
import com.xayy.health.mgt.model.dto.CaseOneDTO;
import com.xayy.health.mgt.model.dto.TelemetryDto;
import com.xayy.health.mgt.model.dto.TimeDto;
import com.xayy.health.mgt.model.vo.CaseOneVo;
import com.xayy.health.mgt.model.vo.DataSourceVo;
import com.xayy.health.mgt.service.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.net.UnknownHostException;
import java.sql.Timestamp;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author shichenlei
 * 事后诊断线上单参数任务
 */
@Service
@Slf4j
public class DiagnoseSingleParamServiceImpl implements DiagnoseSingleParamService {

    @Autowired
    private TaskInfoService taskInfoService;

    @Autowired
    private DiagnoseService diagnoseService;

    @Autowired
    private DataQualityService dataQualityService;

    @Autowired
    private TaskChildInfoService taskChildInfoService;

    @Autowired
    private SatelliteNodeMapper satelliteNodeMapper;

    @Autowired
    private FaultReportService faultReportService;

    /**
     * 事后诊断线上单参数任务
     *
     * @param taskInfo 任务基本信息
     */
    @Override
    public void singleParamRunTask(TaskInfo taskInfo) throws UnknownHostException {
        //单参
        //案例一
        try {
            Date dateEndTime = taskInfo.getDateEndTime();
            if (taskInfo.getRunningStatus().equals(RunningStatusEnum.LONG_RUNNING.getCode())) {
                if (taskInfo.getIsCaseOne() == 0) {
                    //长期运行
                    onlineFaultByLongRun(taskInfo, dateEndTime);
                } else {
                    //单参模型长期
                    onlineFaultByLongRunAndModel(taskInfo, dateEndTime);
                }
            } else if (taskInfo.getRunningStatus().equals(RunningStatusEnum.RUN_IN_STAGES.getCode())) {
                if (taskInfo.getIsCaseOne() == 0) {
                    //阶段运行
                    onlineFaultByRunInStages(taskInfo, dateEndTime);
                } else {
                    //单参模型短期
                    onlineFaultByRunInStagesAndModel(taskInfo);
                }
            }
        } catch (Exception e) {
            faultReportService.faultReport(ModuleEnum.SHZD.getCode(), ModuleEnum.SHZD.getMsg(),"单参任务异常","事后诊断上线运行，单参任务异常");
            log.error("事后诊断上线运行 单参任务异常...", e);
        }
    }

    /**
     * 单参模型短期
     *
     * @param taskInfo 任务信息
     */
    private void onlineFaultByRunInStagesAndModel(TaskInfo taskInfo) throws UnknownHostException {
        Date singleStageStartTime = taskInfo.getEndTime();
        Date singleStageEndTime = DateUtil.offsetMinute(singleStageStartTime, (int) (Double.parseDouble(taskInfo.getCycle()) * 60));
        int compare = DateUtil.compare(taskInfo.getRunInStagesTime(), singleStageEndTime);
        if (compare < 0) {
            setTaskStop(taskInfo, singleStageStartTime);
        } else {
            String singleStageName = taskInfo.getName() + "-" + DateUtil.format(singleStageStartTime, "yyyyMMddHHmmss") + "-" + DateUtil.format(singleStageEndTime, "yyyyMMddHHmmss");
            TaskChildInfo singleStageTaskChildInfo = buildTaskChildInfo(taskInfo, singleStageName, singleStageEndTime, singleStageStartTime);
            taskInfo.setEndTime(singleStageEndTime);
            runTask(taskInfo, false, singleStageTaskChildInfo, null);
        }
    }

    /**
     * 单参模型长期
     *
     * @param taskInfo    任务信息
     * @param dateEndTime 上次任务结束时间
     */
    private void onlineFaultByLongRunAndModel(TaskInfo taskInfo, Date dateEndTime) throws UnknownHostException {
        Date singleStartTime = taskInfo.getLongRunningTime();
        Date singleEndTime = DateUtil.offsetMinute(singleStartTime, (int) (Double.parseDouble(taskInfo.getCycle()) * 60));
        taskInfo.setLongRunningTime(singleEndTime);
        //时候诊断长期运行,运行的时间大于了当前系统的时间，任务就停掉
        DateTime now = DateUtil.date();
        int compare = DateUtil.compare(singleEndTime, now);
        if (compare > 0) {
            setTaskStop(taskInfo, singleStartTime);
        } else {
            String singleName = taskInfo.getName() + "-" + DateUtil.format(singleStartTime, "yyyyMMddHHmmss") + "-" + DateUtil.format(singleEndTime, "yyyyMMddHHmmss");
            TaskChildInfo singleTaskChildInfo = buildTaskChildInfo(taskInfo, singleName, singleEndTime, dateEndTime);
            singleTaskChildInfo.setStartTime(singleStartTime);
            runTask(taskInfo, false, singleTaskChildInfo, null);
        }
    }


    /**
     * 事后诊断单参长期运行
     *
     * @param taskInfo    任务信息
     * @param dateEndTime 结束时间
     */
    private void onlineFaultByLongRun(TaskInfo taskInfo, Date dateEndTime) throws UnknownHostException {
        CaseOneVo caseOneLongVo = getCaseOneVo(taskInfo);
        Map<String, Object> checkResult = assembleCaseOne(caseOneLongVo, taskInfo);
        //首次创建的任务没有dateEndTime，故与开始时间设置相同
        if (null == dateEndTime) {
            dateEndTime = taskInfo.getStartTime();
        }
        //结束时间为长期运行结束时间+周期时间
        Date caseOneLongVoEndTime = DateUtil.offsetMinute(dateEndTime, (int) (Double.parseDouble(taskInfo.getCycle()) * 60));
        taskInfo.setDateEndTime(caseOneLongVoEndTime);
        Date now = DateUtil.date();
        //时候诊断长期运行,运行的时间大于了当前系统的时间，任务就停掉
        runOrStopTask(taskInfo, caseOneLongVoEndTime, now, dateEndTime, checkResult);
    }

    /**
     * 事后诊断单参阶段运行
     *
     * @param taskInfo    任务信息
     * @param dateEndTime 结束时间
     */
    private void onlineFaultByRunInStages(TaskInfo taskInfo, Date dateEndTime) throws UnknownHostException {
        //设置CaseOneVo属性
        CaseOneVo caseOneStageVo = getCaseOneVo(taskInfo);
        //单任务数据组装
        Map<String, Object> checkResult = assembleCaseOne(caseOneStageVo, taskInfo);

        if (null == dateEndTime) {
            dateEndTime = taskInfo.getStartTime();
        }
        Date caseoneEndTime = DateUtil.offsetMinute(dateEndTime, (int) (Double.parseDouble(taskInfo.getCycle()) * 60));
        taskInfo.setDateEndTime(caseoneEndTime);
        //时候诊断长期运行,运行的时间大于了当前系统的时间，任务就停掉
        runOrStopTask(taskInfo, caseoneEndTime, taskInfo.getRunInStagesTime(), dateEndTime, checkResult);
    }

    /**
     * 组装子任务信息
     *
     * @param taskInfo 主任务信息
     * @param taskName 任务名称
     * @param endTime  结束时间
     * @return 子任务信息
     */
    private TaskChildInfo buildTaskChildInfo(TaskInfo taskInfo, String taskName, Date endTime, Date startTime) {
        return TaskChildInfo.builder().taskId(taskInfo.getId()).name(taskName)
                .isCaseOne(taskInfo.getIsCaseOne()).satelliteCode(taskInfo.getSatelliteCode())
                .status(TaskStatusEnum.RUNNING.getCode()).params(taskInfo.getParams())
                .tyTag(taskInfo.getTyTag()).tcTag(taskInfo.getTcTag()).phTag(taskInfo.getPhTag()).startTime(startTime)
                .endTime(endTime).modelName(taskInfo.getModelName()).predNumber(taskInfo.getPredNumber())
                .timeSpan(taskInfo.getTimeSpan()).checkMultiparameter(taskInfo.getCheckMultiparameter()).build();
    }


    /**
     * 任务更新为停止状态
     *
     * @param taskInfo
     */
    private void setTaskStop(TaskInfo taskInfo, Date endTime) {
        taskInfo.setStatus(0);
        taskInfo.setEndTime(endTime);
        taskInfoService.updateByIdSelective(taskInfo);
    }


    /**
     * 设置CaseOneVo属性
     *
     * @param taskInfo 任务基本属性
     * @return CaseOneVo
     */
    private static CaseOneVo getCaseOneVo(TaskInfo taskInfo) {
        CaseOneVo caseOneLongVo = Convert.convert(CaseOneVo.class, taskInfo);
        caseOneLongVo.setVerifyTime(TimeDto.builder().startTime(taskInfo.getStartTime()).endTime(taskInfo.getEndTime()).build());
        caseOneLongVo.setParamCodes(new String[]{taskInfo.getParams()});
        return caseOneLongVo;
    }


    /**
     * 生成子任务，更新任务并且运行任务
     *
     * @param taskInfo             任务基础信息
     * @param startTime            任务上次结束时间
     * @param caseOneLongVoEndTime 任务新的结束时间
     * @param checkResult          获取是否是空数据
     */
    private void updateTaskAndRunTask(TaskInfo taskInfo, Date startTime, Date caseOneLongVoEndTime, Map<String, Object> checkResult) throws UnknownHostException {
        String caseOneLongSecondName = taskInfo.getName() + "-" + DateUtil.format(startTime, "yyyyMMddHHmmss") + "-" + DateUtil.format(caseOneLongVoEndTime, "yyyyMMddHHmmss");
        TaskChildInfo caseOneLongTaskChildInfo = buildTaskChildInfo(taskInfo, caseOneLongSecondName, caseOneLongVoEndTime, startTime);

        boolean emptyDataFlag = (boolean) checkResult.get("emptyData");
        //调用相关算法所需参数
        CaseOneDTO caseOneDTO = (CaseOneDTO) checkResult.get("caseOneDTO");
        runTask(taskInfo, emptyDataFlag, caseOneLongTaskChildInfo, caseOneDTO);
    }


    /**
     * 启动一个任务
     *
     * @param taskInfo                 任务信息
     * @param emptyDataFlag            数据是否是空
     * @param caseOneLongTaskChildInfo 子任务信息
     * @param caseOneDTO               调用算法所需参数
     */
    private void runTask(TaskInfo taskInfo, boolean emptyDataFlag, TaskChildInfo caseOneLongTaskChildInfo, CaseOneDTO caseOneDTO) throws UnknownHostException {
        if (emptyDataFlag) {
            caseOneLongTaskChildInfo.setDataSituation("无");
            caseOneLongTaskChildInfo.setStatus(TaskStatusEnum.ABNORMAL.getCode());
        }
        taskChildInfoService.insert(caseOneLongTaskChildInfo);
        taskInfoService.updateByIdSelective(taskInfo);
        if (!emptyDataFlag) {
            TaskChildInfo caseOneLongTaskChildInfo1 = taskChildInfoService.selectById(caseOneLongTaskChildInfo.getId());
            diagnoseService.asyncExcuteTask(caseOneLongTaskChildInfo1, taskInfo, caseOneDTO);
        }
    }


    /**
     * 启动或停止任务
     *
     * @param x 任务信息
     * @param caseOneLongVoEndTime 结束时间
     * @param now 当前时间
     * @param dateEndTime 运行时间
     * @param checkResult 运行结果
     */
    private void runOrStopTask(TaskInfo x, Date caseOneLongVoEndTime, Date now, Date dateEndTime, Map<String, Object> checkResult) throws UnknownHostException {
        int compareEndTimeAndNow = DateUtil.compare(caseOneLongVoEndTime, now);
        if (compareEndTimeAndNow > 0) {
            setTaskStop(x, dateEndTime);
        } else {
            //生成子任务，更新任务并且运行任务
            updateTaskAndRunTask(x, dateEndTime, caseOneLongVoEndTime, checkResult);
        }
    }


    /**
     * 组装单参数诊断结构
     *
     * @param caseOneVo 单参数入参
     * @param taskInfo  任务信息
     * @return CheckResult<Object>
     */
    private Map<String, Object> assembleCaseOne(CaseOneVo caseOneVo, TaskInfo taskInfo) throws UnknownHostException {
        Map<String, Object> finalMap = new HashMap<>(2);
        //拼接执行的sql
        CheckResult<Object> checkResult = buidExecuteSql(taskInfo,caseOneVo);
        Map data = (Map) checkResult.getData();
        //数据为空情况
        if (null == data.get("data")) {
            finalMap.put("emptyData", true);
            return finalMap;
        }
        List<TbDatatransmissionplan> tbDatatransmissionplanList = JSON.parseArray(JSON.toJSONString(data.get("data")), TbDatatransmissionplan.class);
        if (CollUtil.isEmpty(tbDatatransmissionplanList)) {
            finalMap.put("emptyData", true);
            return finalMap;
        }
        for (TbDatatransmissionplan tbDatatransmissionplan : tbDatatransmissionplanList) {
            String serviceStartTime = tbDatatransmissionplan.getServicestarttime();
            String serviceRetainTime = tbDatatransmissionplan.getServiceretaintime();
            if (StrUtil.isEmpty(serviceStartTime) || StrUtil.isEmpty(serviceRetainTime)) {
                log.info("捕跟开始时间，计划服务时长中存在空值数据...taskInfo={},tbDatatransmissionplan={}", taskInfo, tbDatatransmissionplan);
                throw new HealthException("捕跟开始时间，计划服务时长中存在空值数据");
            }
        }
        List<TbDatatransmissionplan> dataTransMissioPlanList = getTbDatatransmissionplans(tbDatatransmissionplanList);
        if (dataTransMissioPlanList.size() < 2) {
            log.error("从任务计划表里查询到落到检测时间范围数据总数小于2,无法产生间隙数据....taskInfo={}", taskInfo);
            throw new HealthException("从任务计划表里查询到落到检测时间范围数据总数小于2,无法产生间隙数据");
        }
        //设置执行段和间隔段遥测数据
        setIntervalAndTelemetryData(caseOneVo, dataTransMissioPlanList, finalMap);
        return finalMap;
    }


    /**
     * 拼接执行的sql
     *
     * @param taskInfo
     * @return sql执行结果
     */
    private CheckResult<Object> buidExecuteSql(TaskInfo taskInfo,CaseOneVo caseOneVo) throws UnknownHostException {
        DataSourceVo dataSourceVo = new DataSourceVo();
        Date dateEndTime = taskInfo.getDateEndTime();
        if (null == dateEndTime) {
            dateEndTime = taskInfo.getStartTime();
        }
        Date caseOneLongVoEndTime = DateUtil.offsetMinute(dateEndTime, (int) (Double.parseDouble(taskInfo.getCycle()) * 60));
        Timestamp startTime = new Timestamp(dateEndTime.getTime());
        Timestamp endTime = new Timestamp(caseOneLongVoEndTime.getTime());
        StringBuilder sql = new StringBuilder().append("select * from t02_tb_datatransmissionplan where  servicestarttime >= '")
                .append(startTime).append("' and ").append(" servicestarttime <='").append(endTime).append("'").append(" and ").append(" state!= 'deleted' ");
        String satelliteNewId = satelliteNodeMapper.getSatelliteCode(caseOneVo.getSatelliteCode());
        if (null != satelliteNewId) {
            sql .append(" and ").append("satellite_id = '").append(satelliteNewId).append("'");
        }
        dataSourceVo.setSql(sql.toString());
        dataSourceVo.setDbKey("HOLOGRES");
        CheckResult<Object> checkResult = null;
        try {
            checkResult = dataQualityService.excuteRunSql(dataSourceVo);
        } catch (Exception e) {
            log.error("execute Sql errors...dataSourceVo={}", dataSourceVo, e);
            faultReportService.faultReport(ModuleEnum.SHZD.getCode(), ModuleEnum.SHZD.getMsg(),"接口调用异常","接口调用异常");
            throw new HealthException("接口调用异常");
        }
        return checkResult;
    }


    /**
     * 原始数据进行处理
     *
     * @param tbDatatransmissionplanList 处理前数据
     * @return List<TbDatatransmissionplan> 处理后数据
     */
    private List<TbDatatransmissionplan> getTbDatatransmissionplans(List<TbDatatransmissionplan> tbDatatransmissionplanList) {
        List<TbDatatransmissionplan> distinctData = new ArrayList<>(tbDatatransmissionplanList.stream().collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(TbDatatransmissionplan::getServicestarttime)))));
        distinctData.forEach(item -> item.setServicestarttime(item.getServicestarttime().substring(0, 19).replace("T", " ")));
        List<TbDatatransmissionplan> dataTransMissioPlanList = distinctData.stream().sorted(Comparator.comparing(x -> DateUtil.parse(x.getServicestarttime(), "yyyy-MM-dd HH:mm:ss"))).collect(Collectors.toList());
        return dataTransMissioPlanList;
    }

    /**
     * 设置执行段和间隔段遥测数据
     *
     * @param caseOneVo               任务数据
     * @param dataTransMissioPlanList 时间段基础数据
     * @param finalMap                最终返回数据集合
     */
    private void setIntervalAndTelemetryData(CaseOneVo caseOneVo, List<TbDatatransmissionplan> dataTransMissioPlanList, Map<String, Object> finalMap) {
        ArrayList<TimeDto> taskTimeList = new ArrayList<>();
        //任务计划段-遥测
        List<Map<String, List<TelemetryData>>> taskTelemetryDataList = new ArrayList<>();
        //任务计划间隔间隔段-遥测
        List<Map<String, List<TelemetryData>>> taskIntervalTelemetryList = new ArrayList<>();
        HashMap<String, List<Map<String, List<TelemetryData>>>> telemetryMap = new HashMap<>(2);

        CaseOneDTO caseOneDTO = Convert.convert(CaseOneDTO.class, caseOneVo);
        TelemetryDto telemetryDto = TelemetryDto.builder().satelliteCode(caseOneVo.getSatelliteCode()).paramCodes(caseOneVo.getParamCodes()).build();
        //时间段内下一周期的开始时间
        for (int i = 0; i < dataTransMissioPlanList.size(); i++) {
            //判断是否重合标志
            // boolean flag = false;
            //任务段
            if (i < dataTransMissioPlanList.size() - 1) {
                TbDatatransmissionplan tbDatatransmissionplan = dataTransMissioPlanList.get(i);
                String serviceRetainTime = tbDatatransmissionplan.getServiceretaintime();
                Date serviceStartDate = DateUtil.parse(tbDatatransmissionplan.getServicestarttime(), "yyyy-MM-dd HH:mm:ss");
                Date taskEndDate = (Date) (DiagnoseServiceImpl.executeServiceRetailTime(serviceStartDate, serviceRetainTime).getData());

                //下一段任务开始时间
                TbDatatransmissionplan nextTbDatatransmissionplan = dataTransMissioPlanList.get(i + 1);
                Date nextStartDate = DateUtil.parse(nextTbDatatransmissionplan.getServicestarttime(), "yyyy-MM-dd HH:mm:ss");

                //本次结束时间与下次开始时间重合判断
                int endDateAndNextStartCompare = DateUtil.compare(taskEndDate, nextStartDate);
                //如果本次任务结束时间大于下次任务开始时间，则本次结束时间设为下次开始时间
                if (endDateAndNextStartCompare > 0) {
                    taskEndDate = nextStartDate;
                    // flag = true;
                }
                telemetryDto.setStartTime(serviceStartDate);
                telemetryDto.setEndTime(taskEndDate);
                taskTimeList.add(TimeDto.builder().startTime(serviceStartDate).endTime(taskEndDate).build());
                Map<String, List<TelemetryData>> taskPlanTelemetryMap = diagnoseService.selectTelemetryData(telemetryDto);
                taskTelemetryDataList.add(taskPlanTelemetryMap);

                //任务间隙段,任务重合，下次还是任务段
                //if (!flag) {
                telemetryDto.setStartTime(taskEndDate);
                telemetryDto.setEndTime(nextStartDate);
                Map<String, List<TelemetryData>> taskIntervalTelemetryData = diagnoseService.selectTelemetryData(telemetryDto);
                taskIntervalTelemetryList.add(taskIntervalTelemetryData);
                //}
                caseOneDTO.setTaskMultiterminalTime(taskTimeList);
                telemetryMap.put("taskTelemetryDataList", taskTelemetryDataList);
                telemetryMap.put("taskIntervalTelemetry", taskIntervalTelemetryList);
            }
        }

        caseOneDTO.setTelemetryDataList(telemetryMap);
        finalMap.put("caseOneDTO", caseOneDTO);
        finalMap.put("emptyData", false);
    }


}
